package com.alone.jpa.query.condition.impl;

import com.alone.jpa.query.ext.HqlSegmentPredicate;
import com.alone.jpa.query.condition.PredicateBuilder;
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 *
 * @author Alone
 *
 * @see PredicateBuilder
 */
@SuppressWarnings("all")
@FunctionalInterface
public interface SimpleConditionResolver {

    SimpleConditionResolver HQL_RESOLVER = (path, value, criteriaBuilder) -> {
        if (value instanceof String) {
            return new HqlSegmentPredicate((CriteriaBuilderImpl) criteriaBuilder, (String) value);
        }
        throw ILLEGAL_ARGUMENT_EXCEPTION(value.getClass(), String.class);
    };

    SimpleConditionResolver EQ_RESOLVER = (path, value, criteriaBuilder) -> {
        if (value instanceof Path) {
            return criteriaBuilder.equal(path, (Path) value);
        } else {
            return criteriaBuilder.equal(path, value);
        }
    };

    SimpleConditionResolver NE_RESOLVER = (path, value, criteriaBuilder) -> {
        if (value instanceof Path) {
            return criteriaBuilder.notEqual(path, (Path) value);
        } else {
            return criteriaBuilder.notEqual(path, value);
        }
    };

    SimpleConditionResolver LE_RESOLVER = (path, value, criteriaBuilder) -> {
        Class<?> javaType = path.getJavaType();
        if (Number.class.isAssignableFrom(javaType)) {
            if (value instanceof Path) {
                return criteriaBuilder.le(path, (Path) value);
            } else {
                if (value instanceof Number) {
                    return criteriaBuilder.le(path, (Number) value);
                }
            }
        } else if (Comparable.class.isAssignableFrom(javaType)) {
            if (value instanceof Path) {
                return criteriaBuilder.lessThanOrEqualTo(path, (Path) value);
            } else {
                if (value instanceof Comparable) {
                    return criteriaBuilder.lessThanOrEqualTo(path, (Comparable) value);
                }
            }
        } else {
            throw ILLEGAL_ARGUMENT_EXCEPTION(path.getClass(), Path.class, Comparable.class);
        }
        throw ILLEGAL_ARGUMENT_EXCEPTION(value.getClass(), Path.class, Comparable.class);
    };

    SimpleConditionResolver LT_RESOLVER = (path, value, criteriaBuilder) -> {
        Class<?> javaType = path.getJavaType();
        if (Number.class.isAssignableFrom(javaType)) {
            if (value instanceof Path) {
                return criteriaBuilder.lt(path, (Path) value);
            } else {
                if (value instanceof Number) {
                    return criteriaBuilder.lt(path, (Number) value);
                }
            }
        } else if (Comparable.class.isAssignableFrom(javaType)) {
            if (value instanceof Path) {
                return criteriaBuilder.lessThan(path, (Path) value);
            } else {
                if (value instanceof Comparable) {
                    return criteriaBuilder.lessThan(path, (Comparable) value);
                }
            }
        } else {
            throw ILLEGAL_ARGUMENT_EXCEPTION(path.getClass(), Path.class, Comparable.class);
        }
        throw ILLEGAL_ARGUMENT_EXCEPTION(value.getClass(), Path.class, Comparable.class);
    };

    SimpleConditionResolver GE_RESOLVER = (path, value, criteriaBuilder) -> {
        Class<?> javaType = path.getJavaType();
        if (Number.class.isAssignableFrom(javaType)) {
            if (value instanceof Path) {
                return criteriaBuilder.ge(path, (Path) value);
            } else {
                if (value instanceof Number) {
                    return criteriaBuilder.ge(path, (Number) value);
                }
            }
        } else if (Comparable.class.isAssignableFrom(javaType)) {
            if (value instanceof Path) {
                return criteriaBuilder.greaterThanOrEqualTo(path, (Path) value);
            } else {
                if (value instanceof Comparable) {
                    return criteriaBuilder.greaterThanOrEqualTo(path, (Comparable) value);
                }
            }
        } else {
            throw ILLEGAL_ARGUMENT_EXCEPTION(path.getClass(), Path.class, Comparable.class);
        }
        throw ILLEGAL_ARGUMENT_EXCEPTION(value.getClass(), Path.class, Comparable.class);
    };

    SimpleConditionResolver GT_RESOLVER = (path, value, criteriaBuilder) -> {
        Class<?> javaType = path.getJavaType();
        if (Number.class.isAssignableFrom(javaType)) {
            if (value instanceof Path) {
                return criteriaBuilder.gt(path, (Path) value);
            } else {
                if (value instanceof Number) {
                    return criteriaBuilder.gt(path, (Number) value);
                }
            }
        } else if (Comparable.class.isAssignableFrom(javaType)) {
            if (value instanceof Path) {
                return criteriaBuilder.greaterThan(path, (Path) value);
            } else {
                if (value instanceof Comparable) {
                    return criteriaBuilder.greaterThan(path, (Comparable) value);
                }
            }
        } else {
            throw ILLEGAL_ARGUMENT_EXCEPTION(path.getClass(), Path.class, Comparable.class);
        }
        throw ILLEGAL_ARGUMENT_EXCEPTION(value.getClass(), Path.class, Comparable.class);
    };

    SimpleConditionResolver BETWEEN_RESOLVER = (path, value, criteriaBuilder) -> {
        if (value instanceof List) {
            List valueList = (List) value;
            Object o1 = valueList.get(0);
            Object o2 = valueList.get(1);
            if (o1 instanceof Comparable) {
                if (o2 instanceof Comparable) {
                    return criteriaBuilder.between(path, (Comparable) o1, (Comparable) o2);
                } else {
                    throw ILLEGAL_ARGUMENT_EXCEPTION(o2.getClass(), Comparable.class);
                }
            } else {
                throw ILLEGAL_ARGUMENT_EXCEPTION(o1.getClass(), Comparable.class);
            }
        }
        throw ILLEGAL_ARGUMENT_EXCEPTION(value.getClass(), List.class);
    };

    SimpleConditionResolver NOT_BETWEEN_RESOLVER = (path, value, criteriaBuilder) -> {
        return criteriaBuilder.not(SimpleConditionResolver.BETWEEN_RESOLVER.resolve(path, value, criteriaBuilder));
    };

    SimpleConditionResolver IN_RESOLVER = (path, value, criteriaBuilder) -> {
        if (value instanceof Iterable) {
            CriteriaBuilder.In in = criteriaBuilder.in(path);
            for (Object o : ((Iterable) value)) {
                in.value(o);
            }
            return in;
        }
        throw ILLEGAL_ARGUMENT_EXCEPTION(value.getClass(), Iterable.class);
    };

    SimpleConditionResolver NOT_IN_RESOLVER = (path, value, criteriaBuilder) -> {
        return criteriaBuilder.not(SimpleConditionResolver.IN_RESOLVER.resolve(path, value, criteriaBuilder));
    };

    SimpleConditionResolver LIKE_RESOLVER = (path, value, criteriaBuilder) -> criteriaBuilder.like(path, "%" + value.toString() + "%");
    SimpleConditionResolver NOT_LIKE_RESOLVER = (path, value, criteriaBuilder) -> criteriaBuilder.not(criteriaBuilder.like(path, "%" + value.toString() + "%"));
    SimpleConditionResolver LIKE_RIGHT_RESOLVER = (path, value, criteriaBuilder) -> criteriaBuilder.like(path, value.toString() + "%");
    SimpleConditionResolver NOT_LIKE_RIGHT_RESOLVER = (path, value, criteriaBuilder) -> criteriaBuilder.not(criteriaBuilder.like(path, value.toString() + "%"));
    SimpleConditionResolver LIKE_LEFT_RESOLVER = (path, value, criteriaBuilder) -> criteriaBuilder.like(path, "%" + value.toString());
    SimpleConditionResolver NOT_LIKE_LEFT_RESOLVER = (path, value, criteriaBuilder) -> criteriaBuilder.not(criteriaBuilder.like(path, "%" + value.toString()));
    SimpleConditionResolver IS_NULL_RESOLVER = (path, value, criteriaBuilder) -> criteriaBuilder.isNull(path);
    SimpleConditionResolver IS_NOT_NUL_RESOLVER = (path, value, criteriaBuilder) -> criteriaBuilder.isNotNull(path);

    /**
     * 处理成对应的Predicate
     *
     * @param path            字段
     * @param value           值
     * @param criteriaBuilder 条件构建器
     * @return 谓词
     */
    Predicate resolve(Path path, Object value, CriteriaBuilder criteriaBuilder);

    static IllegalArgumentException ILLEGAL_ARGUMENT_EXCEPTION(Class<?> src, Class<?>... targets) {
        IllegalArgumentException res;
        if (targets.length == 0) {
            res = new IllegalArgumentException("can not resolve value type : " + src);
        } else if (targets.length == 1) {
            res = new IllegalArgumentException("can not resolve value type : " + src + ", must be " + targets[0]);
        } else {
            res = new IllegalArgumentException("can not resolve value type : " + src + ", must be "
                    + Arrays.stream(targets).map(Class::toString).collect(Collectors.joining(" or ")));
        }
        return res;
    }

}
